/** \file   monitor.c
 * \brief   The VICE built-in monitor.
 *
 * \author  Daniel Sladic <sladic@eecg.toronto.edu>
 * \author  Ettore Perazzoli <ettore@comm2000.it>
 * \author  Andreas Boose <viceteam@t-online.de>
 * \author  Daniel Kahlin <daniel@kahlin.net>
 * \author  Thomas Giesel <skoe@directbox.com>
 * \author  Bas Wassink <b.wassink@ziggo.nl>
 */

/*
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

#include "vice.h"

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#include "archdep.h"
#include "archdep_defs.h"
#include "cartio.h"
#include "charset.h"
#include "cmdline.h"
#include "console.h"
#include "datasette.h"
#include "drive.h"

#include "interrupt.h"
#include "kbdbuf.h"
#include "lib.h"
#include "util.h"
#include "log.h"
#include "machine.h"
#include "machine-video.h"
#include "mem.h"
#include "mon_breakpoint.h"
#include "mon_disassemble.h"
#include "mon_memmap.h"
#include "mon_memory.h"
#include "asm.h"

#include "mon_parse.h"
#include "mon_register.h"
#include "mon_util.h"
#include "monitor.h"
#include "monitor_network.h"
#include "monitor_binary.h"
#include "montypes.h"

#include "userport_io_sim.h"
#include "joyport_io_sim.h"
#include "joyport.h"

#include "resources.h"
#include "screenshot.h"
#include "sysfile.h"
#include "tape.h"
#include "traps.h"
#include "types.h"
#include "ui.h"
#include "uiapi.h"
#include "uimon.h"
#include "util.h"
#include "video.h"
#include "vsync.h"
#include "sound.h"

/*
   INITBREAK switched to a break point so that the first instruction doesn't
   run when '-initbreak reset' is used.
   Will keep the old code in for awhile incase something breaks somewhere.
*/
/* #define TRAP_INITBREAK */

int mon_stop_output;

enum init_break_mode_t {
    NONE,
    ON_EXECUTE,    /* Monitor will open when executing init_break_address */
    ON_RESET,      /* Monitor will open when booting / resetting */
    ON_READY       /* Monitor will open if it detects 'ready' on vsync */
};

static enum init_break_mode_t init_break_mode = NONE;
static int init_break_address = -1;

/* Defines */

#define MAX_LABEL_LEN 255
#define MAX_MEMSPACE_NAME_LEN 10
#define HASH_ARRAY_SIZE 256
#define HASH_ADDR(x) ((x) % 0xff)
#define OP_JSR 0x20
#define OP_RTI 0x40
#define OP_RTS 0x60

#define ADDR_LIMIT(x) (addr_mask(x))
#define BAD_ADDR (new_addr(e_invalid_space, 0))

#define MONITOR_GET_PC(mem) \
    ((uint16_t)((monitor_cpu_for_memspace[mem]->mon_register_get_val)(mem, e_PC)))

#define MONITOR_GET_OPCODE(mem) (mon_get_mem_val_nosfx(mem, mem == e_comp_space ? mem_get_current_bank_config() : 0, MONITOR_GET_PC(mem)))

console_t *console_log = NULL;

monitor_cartridge_commands_t mon_cart_cmd;

/* Types */

struct symbol_entry {
    uint16_t addr;
    char *name;
    struct symbol_entry *next;
};
typedef struct symbol_entry symbol_entry_t;

struct symbol_table {
    symbol_entry_t *name_list;
    symbol_entry_t *addr_hash_table[HASH_ARRAY_SIZE];
};
typedef struct symbol_table symbol_table_t;

/* Global variables */

/* Defined in file generated by bison. */
extern void set_yydebug(int debug);

static char *last_cmd = NULL;
int exit_mon = 0;
/* flag used by the single-stepping commands to prevent switching forth and back
 * to the emulator window when stepping a single instruction. note that this is
 * different from what keep_monitor_open does :)
 */
static int mon_console_suspend_on_leaving = 1;
/* flag used by the eXit command. it will force the monitor to close regardless
 * of keep_monitor_open.
 */
static int mon_console_close_on_leaving = 0;

int sidefx = 0;
int break_on_dummy_access = 0;
RADIXTYPE default_radix;
MEMSPACE default_memspace = e_comp_space;
static bool inside_monitor = false;
static bool should_pause_on_exit_mon = false;
static bool pause_on_exit_mon = false;
static unsigned int instruction_count;
static bool skip_jsrs;
static int wait_for_return_level;

const char * const _mon_space_strings[] = {
    "Default", "Computer", "Disk8", "Disk9", "Disk10", "Disk11", "<<Invalid>>"
};

static uint16_t watch_load_array[MONITOR_MAX_CHECKPOINTS + 1][NUM_MEMSPACES];
static uint16_t watch_store_array[MONITOR_MAX_CHECKPOINTS + 1][NUM_MEMSPACES];
static unsigned int watch_load_count[NUM_MEMSPACES];
static unsigned int watch_store_count[NUM_MEMSPACES];
static symbol_table_t monitor_labels[NUM_MEMSPACES];
static CLOCK stopwatch_start_time[NUM_MEMSPACES];
monitor_interface_t *mon_interfaces[NUM_MEMSPACES];

MON_ADDR dot_addr[NUM_MEMSPACES];
unsigned char data_buf[256];
unsigned char data_mask_buf[256];
unsigned int data_buf_len;
bool asm_mode;
MON_ADDR asm_mode_addr;
static unsigned int next_or_step_stop;
unsigned monitor_mask[NUM_MEMSPACES];

static bool watch_load_occurred;
static bool watch_store_occurred;

static bool recording;
static FILE *recording_fp;
static char *recording_name;

#define PLAYBACK_MAX_FP_DEPTH 128

static char **playback_filename_stack = NULL;
static FILE **playback_fp_stack = NULL;
static FILE *playback_fp = NULL;
static int playback_fp_stack_size = 0;
static int playback_fp_stack_size_max = 0;

/* if true, control returns to emulator after the last script completes */
static bool playback_exit_after_all_playback = false;

static void playback_end_file(void);

static void monitor_close(bool check_exit);
static int monitor_set_moncommands_file(const char *param, void *extra_param);

/* Disassemble the current opcode on entry.  Used for single step.  */
static int disassemble_on_entry = 0;

/* We now have an array of pointers to the current monitor_cpu_type for each memspace. */
/* This gets initialized in monitor_init(). */
monitor_cpu_type_t *monitor_cpu_for_memspace[NUM_MEMSPACES];

/* A linked list of supported monitor_cpu_types for each memspace */
supported_cpu_type_list_t *monitor_cpu_type_supported[NUM_MEMSPACES];

struct monitor_cpu_type_list_s {
    monitor_cpu_type_t monitor_cpu_type;
    struct monitor_cpu_type_list_s *next_monitor_cpu_type;
};
typedef struct monitor_cpu_type_list_s monitor_cpu_type_list_t;

static monitor_cpu_type_list_t *monitor_cpu_type_list = NULL;

static const char * const cond_op_string[] = {
    "",
    "==",
    "!=",
    ">",
    "<",
    ">=",
    "<=",
    "&&",
    "||",
    "+",
    "-",
    "*",
    "/",
    "&",
    "|"
};

const char * const mon_memspace_string[] = { "default", "C", "8", "9", "10", "11" };

/* CAUTION: must match order in enum t_reg_id, and contain all of its elements */
static const char * const register_string[] = {
/* 6502/65c02 */
    "A",
    "X",
    "Y",
    "PC",
    "SP",
    "FL",
/* z80 */
    "AF",
    "BC",
    "DE",
    "HL",
    "IX",
    "IY",
    "I",
    "R",
    "AF2",
    "BC2",
    "DE2",
    "HL2",
/* C64DTV */
    "R3",
    "R4",
    "R5",
    "R6",
    "R7",
    "R8",
    "R9",
    "R10",
    "R11",
    "R12",
    "R13",
    "R14",
    "R15",
    "ACM",
    "YXM",
/* 65816 */
    "B",
    "C",
    "DPR",
    "PBR",
    "DBR",
    /* "EMUL", */ /* FIXME: not in enum? */
/* 6809 */
    "D",
    "U",
    "DP",
    "E",    /* 658xx/6309/z80 */
/* 6309 */
    "F",
    "W",
    "Q",
    "V",
    "MD",
/* z80 */
    "H",
    "L",
    "IXL",
    "IXH",
    "IYL",
    "IYH",

    /* "CC", */ /* 6x09 */ /* FIXME: same as flags? */

    "RL",   /* Rasterline */
    "CY",   /* Cycle in line */
    "P00",  /* CPU port DDR (6510) */
    "P01"   /* CPU port Data (6510) */
};

bool monitor_is_inside_monitor(void)
{
    return inside_monitor;
}

static bool is_machine_ready(void)
{
    char *s = "READY";
    uint16_t screen_addr, addr;
    uint8_t line_length, cursor_column;
    int i, blinking;

    mem_get_cursor_parameter(&screen_addr, &cursor_column, &line_length, &blinking);

    if (!kbdbuf_is_empty()) {
        return false;
    }

    /* wait until cursor is in the first column */
    if (cursor_column != 0) {
        return false;
    }

    /* now we expect the string in the previous line (typically "READY.") */
    addr = screen_addr - line_length;

    for (i = 0; s[i] != '\0'; i++) {
        if (mem_read_screen((uint16_t)(addr + i) & 0xffff) != s[i] % 64) {
            return false;
        }
    }

    return true;
}

void monitor_reset_hook(void)
{
    if (init_break_mode == ON_RESET) {
        init_break_mode = NONE;

#ifdef TRAP_INITBREAK
        monitor_startup_trap();
#endif
    }
}

void monitor_vsync_hook(void)
{
    if (init_break_mode == ON_READY) {
        /*
         * Check if READY has been printed on the screen ..
         * .. this is also how autostart works.
         */
        if (is_machine_ready()) {
            init_break_mode = NONE;

            monitor_startup_trap();
        }
    }

#ifdef HAVE_NETWORK
    /* check if someone wants to connect remotely to the monitor */
    monitor_check_remote();
    monitor_check_binary();
#endif
}

/* Some local helper functions */
static int find_cpu_type_from_string(const char *cpu_string)
{
    if ((strcasecmp(cpu_string, "6502") == 0) || (strcasecmp(cpu_string, "6510") == 0)) {
        return CPU_6502;
    } else if (strcasecmp(cpu_string, "r65c02") == 0) {
        return CPU_R65C02;
    } else if (strcasecmp(cpu_string, "65816")==0) {
        return CPU_65816;
    } else if (strcasecmp(cpu_string, "h6809") == 0 || strcmp(cpu_string, "6809") == 0) {
        return CPU_6809;
    } else if (strcasecmp(cpu_string, "z80") == 0) {
        return CPU_Z80;
    } else if ((strcasecmp(cpu_string, "6502dtv") == 0) || (strcasecmp(cpu_string, "6510dtv") == 0)) {
        return CPU_6502DTV;
    } else {
        return -1;
    }
}

static monitor_cpu_type_t *monitor_find_cpu_for_memspace(MEMSPACE mem, CPU_TYPE_t cpu)
{
    supported_cpu_type_list_t *ptr;
    if (mem == e_default_space) {
        mem = default_memspace;
    }
    ptr = monitor_cpu_type_supported[mem];
    while (ptr) {
        if (ptr->monitor_cpu_type_p) {
            if (ptr->monitor_cpu_type_p->cpu_type == cpu) {
                return ptr->monitor_cpu_type_p;
            }
        }
        ptr = ptr->next;
    }
    return NULL;
}

static void monitor_print_cpu_types_supported(MEMSPACE mem)
{
    supported_cpu_type_list_t *ptr;
    ptr = monitor_cpu_type_supported[mem];
    while (ptr) {
        if (ptr->monitor_cpu_type_p) {
            switch (ptr->monitor_cpu_type_p->cpu_type) {
                case CPU_6502:
                    mon_out(" 6502");
                    break;
                case CPU_6502DTV:
                    mon_out(" 6502DTV");
                    break;
                case CPU_6809:
                    mon_out(" 6809");
                    break;
                case CPU_Z80:
                    mon_out(" Z80");
                    break;
                case CPU_R65C02:
                    mon_out(" R65C02");
                    break;
                case CPU_65816:
                    mon_out(" 65816/65802");
                    break;
                default:
                    mon_out(" unknown(%u)", ptr->monitor_cpu_type_p->cpu_type);
                    break;
            }
        }
        ptr = ptr->next;
    }
    mon_out("\n");
}

/* *** ADDRESS FUNCTIONS *** */


static void set_addr_memspace(MON_ADDR *a, MEMSPACE m)
{
    *a = new_addr(m, addr_location(*a));
}

bool mon_is_valid_addr(MON_ADDR a)
{
    return addr_memspace(a) != e_invalid_space;
}

bool mon_inc_addr_location(MON_ADDR *a, unsigned inc)
{
    unsigned new_loc = addr_location(*a) + inc;
    *a = new_addr(addr_memspace(*a), addr_mask(new_loc));

    return !(new_loc == addr_location(new_loc));
}

void mon_evaluate_default_addr(MON_ADDR *a)
{
    if (addr_memspace(*a) == e_default_space) {
        set_addr_memspace(a, default_memspace);
    }
}

bool mon_is_in_range(MON_ADDR start_addr, MON_ADDR end_addr, unsigned loc)
{
    unsigned start, end;

    start = addr_location(start_addr);

    if (!mon_is_valid_addr(end_addr)) {
        return (loc == start);
    }

    end = addr_location(end_addr);

    if (end < start) {
        return ((loc >= start) || (loc <= end));
    }

    return ((loc >= start) && (loc <= end));
}

static bool is_valid_addr_range(MON_ADDR start_addr, MON_ADDR end_addr)
{
    if (addr_memspace(start_addr) == e_invalid_space) {
        return false;
    }

    if ((addr_memspace(start_addr) != addr_memspace(end_addr)) &&
        ((addr_memspace(start_addr) != e_default_space) ||
         (addr_memspace(end_addr) != e_default_space))) {
        return false;
    }
    return true;
}

static unsigned get_range_len(MON_ADDR addr1, MON_ADDR addr2)
{
    uint16_t start, end;
    unsigned len = 0;

    start = addr_location(addr1);
    end = addr_location(addr2);

    if (start <= end) {
        len = end - start + 1;
    } else {
        len = (0xffff - start) + end + 1;
    }

    return len;
}

long mon_evaluate_address_range(MON_ADDR *start_addr, MON_ADDR *end_addr,
                                bool must_be_range, uint16_t default_len)
{
    size_t len = default_len;

    /* Check if we DEFINITELY need a range. */
    if (!is_valid_addr_range(*start_addr, *end_addr) && must_be_range) {
        return -1;
    }

    if (is_valid_addr_range(*start_addr, *end_addr)) {
        MEMSPACE mem1, mem2;
        /* Resolve any default memory spaces. We wait until now because we
         * need both addresses - if only 1 is a default, use the other to
         * resolve the memory space.
         */
        mem1 = addr_memspace(*start_addr);
        mem2 = addr_memspace(*end_addr);

        if (mem1 == e_default_space) {
            if (mem2 == e_default_space) {
                set_addr_memspace(start_addr, default_memspace);
                set_addr_memspace(end_addr, default_memspace);
            } else {
                if (mem2 != e_invalid_space) {
                    set_addr_memspace(start_addr, mem2);
                } else {
                    set_addr_memspace(start_addr, default_memspace);
                }
            }
        } else {
            if (mem2 == e_default_space) {
                set_addr_memspace(end_addr, mem1);
            } else {
                if (mem2 != e_invalid_space) {
                    if (!(mem1 == mem2)) {
                        log_error(LOG_DEFAULT, "Invalid memspace!");
                        return 0;
                    }
                } else {
                    log_error(LOG_DEFAULT, "Invalid memspace!");
                    return 0;
                }
            }
        }

        len = get_range_len(*start_addr, *end_addr);
    } else {
        if (!mon_is_valid_addr(*start_addr)) {
            *start_addr = dot_addr[(int)default_memspace];
        } else {
            mon_evaluate_default_addr(start_addr);
        }

        if (!mon_is_valid_addr(*end_addr)) {
            *end_addr = *start_addr;
            mon_inc_addr_location(end_addr, (int)len);
        } else {
            set_addr_memspace(end_addr, addr_memspace(*start_addr));
            len = get_range_len(*start_addr, *end_addr);
        }
    }

    return len;
}


/* *** REGISTER AND MEMORY OPERATIONS *** */

mon_reg_list_t *mon_register_list_get(int mem)
{
    return monitor_cpu_for_memspace[mem]->mon_register_list_get(mem);
}

bool check_drive_emu_level_ok(int drive_num)
{
    if (drive_num < 8 || drive_num > 11) {
        return false;
    }

    if (mon_interfaces[monitor_diskspace_mem(drive_num - 8)] == NULL) {
        mon_out("True drive emulation not supported for this machine.\n");
        return false;
    }

    return true;
}

void monitor_cpu_type_set(const char *cpu_type)
{
    int searchcpu;
    monitor_cpu_type_t *monitor_cpu_type_p = NULL;

    searchcpu = find_cpu_type_from_string(cpu_type);
    if (searchcpu > -1) {
        monitor_cpu_type_p = monitor_find_cpu_for_memspace(default_memspace, searchcpu);
    }
    if (monitor_cpu_type_p) {
        monitor_cpu_for_memspace[default_memspace] = monitor_cpu_type_p;
        uimon_notify_change();
    } else {
        if (strcmp(cpu_type, "") != 0) {
            mon_out("Unknown CPU type `%s'\n", cpu_type);
        }
        mon_out("This device (%s) supports the following CPU types:", _mon_space_strings[default_memspace]);
        monitor_print_cpu_types_supported(default_memspace);
    }
}

/* basically above code but faster as the type is passed; used by x128 */
void monitor_cpu_type_set_value(int searchcpu)
{
    monitor_cpu_type_t *monitor_cpu_type_p = NULL;

    if (searchcpu > -1) {
        monitor_cpu_type_p = monitor_find_cpu_for_memspace(default_memspace, searchcpu);
    }
    if (monitor_cpu_type_p) {
        monitor_cpu_for_memspace[default_memspace] = monitor_cpu_type_p;
        uimon_notify_change();
    }
}

/* return the bank number for a literal bank name in the given memspace.
   returns -1 if the memspace has no banking */
int mon_banknum_from_bank(MEMSPACE mem, const char *bankname)
{
    int newbank;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    if (mon_interfaces[mem]->mem_bank_from_name == NULL) {
        mon_out("Banks not available in this memspace\n");
        return -1;
    }

    newbank = mon_interfaces[mem]->mem_bank_from_name(bankname);
    if (newbank < 0) {
        mon_out("Unknown bank name '%s'\n", bankname);
        return 0;
    }
    return newbank;
}

void mon_bank(MEMSPACE mem, const char *bankname)
{
    if (mem == e_default_space) {
        mem = default_memspace;
    }

    if (!mon_interfaces[mem]->mem_bank_list) {
        mon_out("Banks not available in this memspace\n");
        return;
    }

    if (bankname == NULL) {
        const char **bnp;
        int current_bank_index = -1;
        char *first_bank_name = NULL;

        bnp = mon_interfaces[mem]->mem_bank_list();
        mon_out("Available banks (some may be equivalent to others):\n");
        while (*bnp) {
            int bank = mon_interfaces[mem]->mem_bank_from_name(*bnp);
            int bank_flags = mon_get_bank_flags_for_bank(mem, bank);
            unsigned int bank_index = mon_get_bank_index_for_bank(mem, bank);
            /* printf("bankname:%s bank:%d flags:%d index:%u\n", *bnp, bank, bank_flags, bank_index); */
            if (!(bank_flags & MEM_BANK_ISARRAY)) {
                mon_out("%s%s \t", (bank == mon_interfaces[mem]->current_bank) ? "*" : "", *bnp);
            } else {
                if (bank == mon_interfaces[mem]->current_bank) {
                    current_bank_index = bank_index;
                }
                if (bank_flags & MEM_BANK_ISARRAYFIRST) {
                    first_bank_name = lib_strdup(*bnp);
                }
                if (bank_flags & MEM_BANK_ISARRAYLAST) {
                    if (current_bank_index > -1) {
                        mon_out("*%s-%02x(%02x) \t",
                                first_bank_name, bank_index, (unsigned)current_bank_index);
                    } else {
                        mon_out("%s-%02x \t", first_bank_name, bank_index);
                    }
                    current_bank_index = -1;
                    lib_free(first_bank_name);
                }
            }
            bnp++;
        }
        mon_out("\n");
    } else {
        int newbank = mon_interfaces[mem]->mem_bank_from_name(bankname);
        if (newbank < 0) {
            mon_out("Unknown bank name `%s'\n", bankname);
            return;
        }
        mon_interfaces[mem]->current_bank = newbank;
        mon_interfaces[mem]->current_bank_index = mon_get_bank_index_for_bank(mem, newbank);
        /* printf("mon_bank: MEMSPACE: %u, newbank: %d banknum; %d\n", mem, newbank, bankindex); */
    }
}

/*! \internal \brief check if the bank number is valid for memspace

 \param mem
   The memspace of the bank

 \param banknum
   The number of the bank

 \return
   -1: Memspace doesn't have banks
   0: Bank number invalid
   1: Bank number valid
*/
const int mon_banknum_validate(MEMSPACE mem, int banknum)
{
    const int *banknums;

    if(!mon_interfaces[mem]->mem_bank_list_nos) {
        mon_out("Banks not available in this memspace\n");
        return -1;
    }

    banknums = mon_interfaces[mem]->mem_bank_list_nos();
    while(*banknums != -1) {
        if(banknum == *banknums) {
            return 1;
        }
        ++banknums;
    }

    return 0;
}

/* return the literal bank name for a given bank in a given memspace */
const char *mon_get_bank_name_for_bank(MEMSPACE mem, int banknum)
{
    const char **bnp = NULL;

    if (!mon_interfaces[mem]->mem_bank_list) {
        return NULL;
    }

    bnp = mon_interfaces[mem]->mem_bank_list();
    while (*bnp) {
        if (mon_interfaces[mem]->mem_bank_from_name(*bnp) == banknum) {
            return *bnp;
        }
        bnp++;
    }
    return NULL;
}

/* return the current bank index for a given bank in a given memspace */
int mon_get_bank_index_for_bank(MEMSPACE mem, int banknum)
{
    if (mon_interfaces[mem]->mem_bank_index_from_bank) {
        return mon_interfaces[mem]->mem_bank_index_from_bank(banknum);
    }
    log_warning(LOG_DEFAULT, "FIXME: mon_interfaces->mem_bank_index_from_bank not implemented");
    return -1;
}

/* return the current bank flags for a given bank in a given memspace */
int mon_get_bank_flags_for_bank(MEMSPACE mem, int banknum)
{
    if (mon_interfaces[mem]->mem_bank_flags_from_bank) {
        return mon_interfaces[mem]->mem_bank_flags_from_bank(banknum);
    }
    log_warning(LOG_DEFAULT, "FIXME: mon_interfaces->mem_bank_flags_from_bank not implemented");
    return 0;
}

const char *mon_get_current_bank_name(MEMSPACE mem)
{
    return mon_get_bank_name_for_bank(mem, mon_interfaces[mem]->current_bank);
}

#if 0
static const int mon_get_current_bank_index(MEMSPACE mem)
{
    return mon_get_bank_index_for_bank(mem, mon_interfaces[mem]->current_bank);
}
#endif

/*
    main entry point for the monitor to read a value from memory

    mem_bank_peek and mem_bank_read are set up in src/drive/drivecpu.c,
    src/drive/drivecpu65c02.c, src/mainc64cpu.c, src/mainviccpu.c,
    src/maincpu.c, src/main65816cpu.c
*/

uint8_t mon_get_mem_val_ex(MEMSPACE mem, int bank, uint16_t mem_addr)
{
    if (monitor_diskspace_dnr(mem) >= 0) {
        if (!check_drive_emu_level_ok(monitor_diskspace_dnr(mem) + 8)) {
            return 0;
        }
    }

    if ((sidefx == 0) && (mon_interfaces[mem]->mem_bank_peek != NULL)) {
        return mon_interfaces[mem]->mem_bank_peek(bank, mem_addr, mon_interfaces[mem]->context);
    } else {
        if (sidefx == 0) {
            log_error(LOG_DEFAULT, "mon_get_mem_val_ex: mem_bank_peek() not implemented for memspace %u.", mem);
        }
        return mon_interfaces[mem]->mem_bank_read(bank, mem_addr, mon_interfaces[mem]->context);
    }
}

uint8_t mon_get_mem_val(MEMSPACE mem, uint16_t mem_addr)
{
    return mon_get_mem_val_ex(mem, mon_interfaces[mem]->current_bank, mem_addr);
}

/* the _nosfx variants must be used when the monitor must absolutely not cause
   any sideeffect, be it emulated I/O or (re)triggering checkpoints */
uint8_t mon_get_mem_val_ex_nosfx(MEMSPACE mem, int bank, uint16_t mem_addr)
{
    if (monitor_diskspace_dnr(mem) >= 0) {
        if (!check_drive_emu_level_ok(monitor_diskspace_dnr(mem) + 8)) {
            return 0;
        }
    }

    if (mon_interfaces[mem]->mem_bank_peek != NULL) {
        return mon_interfaces[mem]->mem_bank_peek(bank, mem_addr, mon_interfaces[mem]->context);
    } else {
        log_error(LOG_DEFAULT, "mon_get_mem_val_ex_nosfx: mem_bank_peek() not implemented for memspace %u.", mem);
        return mon_interfaces[mem]->mem_bank_read(bank, mem_addr, mon_interfaces[mem]->context);
    }
}

uint8_t mon_get_mem_val_nosfx(MEMSPACE mem, uint16_t mem_config, uint16_t mem_addr)
{
    if (monitor_diskspace_dnr(mem) >= 0) {
        if (!check_drive_emu_level_ok(monitor_diskspace_dnr(mem) + 8)) {
            return 0;
        }
    }

    if (mon_interfaces[mem]->mem_peek_with_config != NULL) {
        return mon_interfaces[mem]->mem_peek_with_config(mem_config, mem_addr, mon_interfaces[mem]->context);
    } else if (mon_interfaces[mem]->mem_bank_peek != NULL) {
        return mon_interfaces[mem]->mem_bank_peek(mon_interfaces[mem]->current_bank, mem_addr, mon_interfaces[mem]->context);
    } else {
        return mon_interfaces[mem]->mem_bank_read(mon_interfaces[mem]->current_bank, mem_addr, mon_interfaces[mem]->context);
    }


    return mon_get_mem_val_ex_nosfx(mem, mon_interfaces[mem]->current_bank, mem_addr);
}

void mon_get_mem_block_ex(MEMSPACE mem, int bank, uint16_t start, uint16_t end, uint8_t *data)
{
    int i;
    for (i = 0; i <= end; i++) {
        data[i] = mon_get_mem_val_ex(mem, bank, (uint16_t)(start + i));
    }
}

void mon_get_mem_block(MEMSPACE mem, uint16_t start, uint16_t end, uint8_t *data)
{
    mon_get_mem_block_ex(mem, mon_interfaces[mem]->current_bank, start, end, data);
}

/*
    main entry point for the monitor to write a value to memory

    mem_bank_peek and mem_bank_read are set up in src/drive/drivecpu.c,
    src/drive/drivecpu65c02.c, src/mainc64cpu.c, src/mainviccpu.c,
    src/maincpu.c, src/main65816cpu.c
*/

void mon_set_mem_val(MEMSPACE mem, uint16_t mem_addr, uint8_t val)
{
    int bank;

    bank = mon_interfaces[mem]->current_bank;

    if (monitor_diskspace_dnr(mem) >= 0) {
        if (!check_drive_emu_level_ok(monitor_diskspace_dnr(mem) + 8)) {
            return;
        }
    }

    if ((sidefx == 0) && (mon_interfaces[mem]->mem_bank_poke != NULL)) {
        mon_interfaces[mem]->mem_bank_poke(bank, mem_addr, val, mon_interfaces[mem]->context);
    } else {
        mon_interfaces[mem]->mem_bank_write(bank, mem_addr, val, mon_interfaces[mem]->context);
    }
}

/*! \internal \brief set a byte of memory in a specific bank, not the current.

 \param mem
   Memspace of bank

 \param bank
   The bank number

 \param mem_addr
   The 16 bit memory address

 \param val
   The byte to write

*/
void mon_set_mem_val_ex(MEMSPACE mem, int bank, uint16_t mem_addr, uint8_t val)
{
    if (monitor_diskspace_dnr(mem) >= 0) {
        if (!check_drive_emu_level_ok(monitor_diskspace_dnr(mem) + 8)) {
            return;
        }
    }

    if ((sidefx == 0) && (mon_interfaces[mem]->mem_bank_poke != NULL)) {
        mon_interfaces[mem]->mem_bank_poke(bank, mem_addr, val, mon_interfaces[mem]->context);
    } else {
        mon_interfaces[mem]->mem_bank_write(bank, mem_addr, val, mon_interfaces[mem]->context);
    }
}

/* exit monitor  */
void mon_jump(MON_ADDR addr)
{
    mon_evaluate_default_addr(&addr);
    (monitor_cpu_for_memspace[addr_memspace(addr)]->mon_register_set_val)(addr_memspace(addr), e_PC, (uint16_t)(addr_location(addr)));
    exit_mon = 1;
}

/* exit monitor  */
void mon_go(void)
{
    exit_mon = 1;

    if (should_pause_on_exit_mon || ui_pause_active()) {
        should_pause_on_exit_mon = false;
        pause_on_exit_mon = true;
    }
}

/* exit monitor, close monitor window  */
void mon_exit(void)
{
    exit_mon = 1;
    mon_console_close_on_leaving = 1;

    if (should_pause_on_exit_mon || ui_pause_active()) {
        should_pause_on_exit_mon = false;
        pause_on_exit_mon = true;
    }
}

/* If we want 'quit' for OS/2 I couldn't leave the emulator by calling exit(0)
   So I decided to skip this (I think it's unnecessary for OS/2

    Update: we don't support OS/2 anymore, so simply set exit_mon to 2.
    --compyx 2020-09-22
*/
void mon_quit(void)
{
    exit_mon = 2;
}

void mon_keyboard_feed(const char *string)
{
    kbdbuf_feed_string(string);
}

/* *** ULTILITY FUNCTIONS *** */

void mon_print_bin(int val, char on, char off)
{
    int divisor;
    char digit;

    if (val > 0xfff) {
        divisor = 0x8000;
    } else if (val > 0xff) {
        divisor = 0x800;
    } else {
        divisor = 0x80;
    }

    while (divisor) {
        digit = (val & divisor) ? on : off;
        mon_out("%c", digit);
        if (divisor == 0x100) {
            mon_out(" ");
        }
        divisor /= 2;
    }
}

static void print_hex(int val)
{
    mon_out(val > 0xff ? "$%04x\n" : "$%02x\n", (unsigned int)val);
}

static void print_octal(int val)
{
    mon_out(val > 0777 ? "0%06o\n" : "0%03o\n", (unsigned int)val);
}


void mon_print_convert(int val)
{
    mon_out("+%d\n", val);
    print_hex(val);
    print_octal(val);
    mon_out("%%");
    mon_print_bin(val, '1', '0');
    mon_out("\n");
}

void mon_clear_buffer(void)
{
    data_buf_len = 0;
}

void mon_add_number_to_buffer(int number)
{
    unsigned int i = data_buf_len;
    data_buf[data_buf_len++] = (number & 0xff);
    if (number > 0xff) {
        data_buf[data_buf_len++] = ((number >> 8) & 0xff);
    }
    data_buf[data_buf_len] = '\0';

    for (; i < data_buf_len; i++) {
        data_mask_buf[i] = 0xff;
    }
}

void mon_add_number_masked_to_buffer(int number, int mask)
{
    data_buf[data_buf_len] = (number & 0xff);
    data_mask_buf[data_buf_len] = mask;
    data_buf_len++;
    data_buf[data_buf_len] = '\0';
}

void mon_add_string_to_buffer(char *str)
{
    unsigned int i = data_buf_len;
    strcpy((char *) &(data_buf[data_buf_len]), str);
    data_buf_len += (unsigned int)strlen(str);
    data_buf[data_buf_len] = '\0';
    lib_free(str);

    for (; i < data_buf_len; i++) {
        data_mask_buf[i] = 0xff;
    }
}

static monitor_cpu_type_list_t *monitor_list_new(void)
{
    return (monitor_cpu_type_list_t *)lib_malloc(
        sizeof(monitor_cpu_type_list_t));
}

static void montor_list_destroy(monitor_cpu_type_list_t *list)
{
    lib_free(list);
}

unsigned mon_disassemble_oneline(MEMSPACE memspace, uint16_t mem_config, uint16_t addr) {
    const int hex_mode = 1;
    unsigned opc_size;
    uint8_t opc;
    uint16_t loc;
    uint8_t p3;

    opc  = mon_get_mem_val_nosfx(memspace, mem_config, addr);
    loc  = mon_get_mem_val_nosfx(memspace, mem_config, addr + 1)
         | mon_get_mem_val_nosfx(memspace, mem_config, addr + 2) << 8;
    p3   = mon_get_mem_val_nosfx(memspace, mem_config, addr + 3);

    mon_out(".%s:%04x   %s",
            mon_memspace_string[memspace],
            addr,
            mon_disassemble_to_string_ex(memspace, addr,
                                         opc,
                                         loc & 0xff,
                                         loc >> 8,
                                         p3,
                                         hex_mode,
                                         &opc_size));

    if (opc == 0x6c) {
        uint16_t dest = mon_get_mem_val_nosfx(memspace, mem_config, loc)
                      | mon_get_mem_val_nosfx(memspace, mem_config, loc + 1) << 8;

        mon_out(" ; -> $%04X", dest);
    }

    mon_out("\n");

    return opc_size;
}

void mon_backtrace(void)
{
    uint16_t sp, i, pc, addr;

    extern uint16_t callstack_pc_dst[];
    extern uint16_t callstack_pc_src[];
    extern uint16_t callstack_memory_bank_config[];
    extern uint8_t  callstack_sp[];
    extern unsigned callstack_size;
    /* FIXME: memspace should be passed as an argument to this function */
    MEMSPACE mem = default_memspace;
    if (mem == e_default_space) {
        mem = default_memspace;
    }
    /* FIXME: only computer memspace can work right now */
    if (mem != e_comp_space) {
        mon_out("Sorry, backtrace is only implemented for computer memspace.\n");
        return;
    }

    pc = (monitor_cpu_for_memspace[default_memspace]->mon_register_get_val)(default_memspace, e_PC);
    sp = (monitor_cpu_for_memspace[default_memspace]->mon_register_get_val)(default_memspace, e_SP);

    mon_out("             PC        ");
    mon_disassemble_oneline(default_memspace, mem_get_current_bank_config(), pc);

    for (i = callstack_size-1; (int16_t)i >= 0; i--) {
        uint16_t addr_src = callstack_pc_src[i];
        uint16_t addr_dst = callstack_pc_dst[i];

        /* get current stack value */
        addr =   mon_get_mem_val_nosfx(default_memspace, callstack_memory_bank_config[i], callstack_sp[i] + 0x100 + 1)
               | mon_get_mem_val_nosfx(default_memspace, callstack_memory_bank_config[i], callstack_sp[i] + 0x100 + 2) << 8;

        if (addr_src >= 0xfffa && !(addr_src & 1)) {
            switch(addr_src) {
            case 0xfffa: mon_out("NMI "); break;
            case 0xfffc: mon_out("RST "); break;
            case 0xfffe: mon_out("IRQ "); break;
            }
        } else {
            mon_out("%04x", (unsigned)(addr_src - 2));
            addr -= 2; /* print the JSR instruction */
        }
        mon_out(" -> %04x", addr_dst);
        mon_out(" [SP +%3d] ", callstack_sp[i] - sp + 1);

        mon_disassemble_oneline(default_memspace, callstack_memory_bank_config[i], addr);
    }
}

void mon_screenshot_save(const char *filename, int format)
{
    const char *drvname;

    switch (format) {
        case 1:
            drvname = "PCX";
            break;
        case 2:
            drvname = "PNG";
            break;
        case 3:
            drvname = "GIF";
            break;
        case 4:
            drvname = "IFF";
            break;
        default:
            drvname = "BMP";
            break;
    }
    if (screenshot_save(drvname, filename, machine_video_canvas_get(0))) {
        mon_out("Failed.\n");
    }
}


/** \brief  Display current working directory
 */
void mon_show_pwd(void)
{
    char *p = archdep_current_dir();
    mon_out("%s\n", p);
    lib_free(p);
}

void mon_show_dir(const char *path)
{
    archdep_dir_t *dir;
    const char *name;
    char *mpath;
    char *fullname;

    if (path) {
        mpath = lib_strdup(path);
    } else {
        mpath = archdep_current_dir();
    }
    mon_out("Displaying directory: `%s'\n", mpath);

    dir = archdep_opendir(mpath, ARCHDEP_OPENDIR_ALL_FILES);
    if (!dir) {
        mon_out("Couldn't open directory.\n");
        lib_free(mpath);
        return;
    }

    while ((name = archdep_readdir(dir))) {
        size_t len;
        unsigned int isdir;
        int ret;
        if (path) {
            fullname = util_concat(path, ARCHDEP_DIR_SEP_STR, name, NULL);
            ret = archdep_stat(fullname, &len, &isdir);
            lib_free(fullname);
        } else {
            ret = archdep_stat(name, &len, &isdir);
        }
        if (!ret) {
            if (isdir) {
                mon_out("     <dir> %s\n", name);
            } else {
                mon_out("%10"PRI_SIZE_T" %s\n", len, name);
            }
        } else {
            mon_out("     %-20s ?????\n", name);
        }
    }
    lib_free(mpath);
    archdep_closedir(dir);
}

void mon_resource_get(const char *name)
{
    switch (resources_query_type(name)) {
        case RES_INTEGER:
        case RES_STRING:
            mon_out("%s\n", resources_write_item_to_string(name, ""));
            break;
        default:
            mon_out("Unknown resource \"%s\".\n", name);
            return;
    }
}

void mon_resource_set(const char *name, const char *value)
{
    switch (resources_query_type(name)) {
        case RES_INTEGER:
        case RES_STRING:
            if (resources_set_value_string(name, value)) {
                mon_out("Failed.\n");
            }
            break;
        default:
            mon_out("Unknown resource \"%s\".\n", name);
            return;
    }
}

void mon_reset_machine(int type)
{
    switch (type) {
        case 1:
            machine_trigger_reset(MACHINE_RESET_MODE_POWER_CYCLE);
            exit_mon = 1;
            break;
        case 8:
        case 9:
        case 10:
        case 11:
            drive_cpu_trigger_reset(type - 8);
            break;
        default:
            machine_trigger_reset(MACHINE_RESET_MODE_RESET_CPU);
            exit_mon = 1;
            break;
    }
}

void mon_tape_ctrl(int port, int command)
{
    if ((command < 0) || (command > 6)) {
        mon_out("Unknown command.\n");
    } else {
        datasette_control(port, command);
    }
}

void mon_tape_offs(int port, int offset)
{
    tape_image_t *tape_image = tape_image_dev[port];

    if (tape_image && tape_image->data) {
        if (offset < 0) {
            offset = (int)tape_get_offset(tape_image);
            mon_out("Current tape offset is: %d\n", offset);
        } else {
            mon_out("Setting tape to offset: %d\n", offset);
            tape_seek_to_offset(tape_image, offset);
        }
    } else {
        mon_out("No tape attached.\n");
    }
}

void mon_cart_freeze(void)
{
    if (mon_cart_cmd.cartridge_trigger_freeze != NULL) {
        (mon_cart_cmd.cartridge_trigger_freeze)();
    } else {
        mon_out("Unsupported.\n");
    }
}

IO_SIM_RESULT mon_userport_set_output(int value)
{
    if (machine_class == VICE_MACHINE_CBM5x0) {
        mon_out("Unsupported.\n");
        return e_IO_SIM_RESULT_GENERAL_FAILURE;
    } else if (value >= 0x00 && value <= 0xff) {
        userport_io_sim_set_pbx_out_lines((uint8_t)value);
        return e_IO_SIM_RESULT_OK;
    } else {
        mon_out("Illegal value.\n");
        return e_IO_SIM_RESULT_ILLEGAL_VALUE;
    }

    return 0;
}

IO_SIM_RESULT mon_joyport_set_output(int port, int value)
{
    int command_ok = 0;
    int port_ok = 1;

    if (value < 0x00 || value > 0xff) {
        mon_out("Illegal value.\n");
        return e_IO_SIM_RESULT_ILLEGAL_VALUE;
    }

    switch (machine_class) {
        case VICE_MACHINE_C64:
        case VICE_MACHINE_C128:
        case VICE_MACHINE_CBM5x0:
        case VICE_MACHINE_C64DTV:
        case VICE_MACHINE_C64SC:
        case VICE_MACHINE_SCPU64:
            if (port == JOYPORT_1 || port == JOYPORT_2) {
                command_ok = 1;
            } else {
                port_ok = 0;
            }
            break;
        case VICE_MACHINE_VIC20:
            if (port == JOYPORT_1) {
                command_ok = 1;
            } else {
                port_ok = 0;
            }
            break;
        case VICE_MACHINE_PLUS4:
            if (port == JOYPORT_1 || port == JOYPORT_2 || port == JOYPORT_PLUS4_SIDCART) {
                command_ok = 1;
            } else {
                port_ok = 0;
            }
            break;
    }

    if (command_ok) {
        joyport_io_sim_set_out_lines((uint8_t)value, port);
        return e_IO_SIM_RESULT_OK;
    } else if (!port_ok) {
        mon_out("Illegal port.\n");
        return e_IO_SIM_RESULT_ILLEGAL_PORT;
    } else {
        mon_out("Unsupported.\n");
        return e_IO_SIM_RESULT_GENERAL_FAILURE;
    }
}

void mon_export(void)
{
    if (mon_cart_cmd.export_dump != NULL) {
        (mon_cart_cmd.export_dump)();
    } else {
        mon_out("Unsupported.\n");
    }
}

void mon_stopwatch_show(const char *prefix, const char *suffix)
{
    unsigned long t;
    monitor_interface_t *vice_interface;
    vice_interface = mon_interfaces[default_memspace];
    t = (unsigned long)
        (*vice_interface->clk - stopwatch_start_time[default_memspace]);
    mon_out("%s%10lu%s", prefix, t, suffix);
}

void mon_stopwatch_reset(void)
{
    monitor_interface_t *vice_interface;
    vice_interface = mon_interfaces[default_memspace];
    stopwatch_start_time[default_memspace] = *vice_interface->clk;
    mon_out("Stopwatch reset to 0.\n");
}

/* Local helper functions for building the lists */
static monitor_cpu_type_t *find_monitor_cpu_type(CPU_TYPE_t cputype)
{
    monitor_cpu_type_list_t *list_ptr = monitor_cpu_type_list;
    while (list_ptr->monitor_cpu_type.cpu_type != cputype) {
        list_ptr = list_ptr->next_monitor_cpu_type;
        if (!list_ptr) {
            return NULL;
        }
    }
    return &(list_ptr->monitor_cpu_type);
}

static void add_monitor_cpu_type_supported(supported_cpu_type_list_t **list_ptr, monitor_cpu_type_t *mon_cpu_type)
{
    supported_cpu_type_list_t *element_ptr;
    if (mon_cpu_type) {
        element_ptr = lib_malloc(sizeof(supported_cpu_type_list_t));
        element_ptr->next = *list_ptr;
        element_ptr->monitor_cpu_type_p = mon_cpu_type;
        *list_ptr = element_ptr;
    }
}

static void find_supported_monitor_cpu_types(supported_cpu_type_list_t **list_ptr, monitor_interface_t *mon_interface)
{
    if (mon_interface->h6809_cpu_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_6809));
    }
    if (mon_interface->z80_cpu_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_Z80));
    }
    if (mon_interface->dtv_cpu_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_6502DTV));
    }
    if (mon_interface->cpu_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_6502));
    }
    if (mon_interface->cpu_R65C02_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_R65C02));
    }
    if (mon_interface->cpu_65816_regs) {
        add_monitor_cpu_type_supported(list_ptr, find_monitor_cpu_type(CPU_65816));
    }
}

/* *** MISC COMMANDS *** */

monitor_cpu_type_t *monitor_find_cpu_type_from_string(const char *cpu_type)
{
    int cpu;
    cpu = find_cpu_type_from_string(cpu_type);
    if (cpu < 0) {
        return NULL;
    }
    return find_monitor_cpu_type(cpu);
}

void monitor_init(monitor_interface_t *maincpu_interface_init,
                  monitor_interface_t *drive_interface_init[],
                  monitor_cpu_type_t **asmarray)
{
    int i, j;
    unsigned int dnr;
    monitor_cpu_type_list_t *monitor_cpu_type_list_ptr;

    /* Set to 1 to get parsing debug information via "yydebug". Requires YYDEBUG
       defined as 1 in mon_parse.y (which is why we can keep it 1 here all times) */
    set_yydebug(1);

    sidefx = e_OFF;
    default_radix = e_hexadecimal;
    default_memspace = e_comp_space;
    instruction_count = 0;
    skip_jsrs = false;
    wait_for_return_level = 0;
    mon_breakpoint_init();
    data_buf_len = 0;
    asm_mode = 0;
    next_or_step_stop = 0;
    recording = false;

    monitor_cpu_type_list = monitor_list_new();
    monitor_cpu_type_list_ptr = monitor_cpu_type_list;

    i = 0;
    while (asmarray[i] != NULL) {
        memcpy(&(monitor_cpu_type_list_ptr->monitor_cpu_type),
               asmarray[i], sizeof(monitor_cpu_type_t));
        monitor_cpu_type_list_ptr->next_monitor_cpu_type = monitor_list_new();
        monitor_cpu_type_list_ptr
            = monitor_cpu_type_list_ptr->next_monitor_cpu_type;
        monitor_cpu_type_list_ptr->next_monitor_cpu_type = NULL;
        i++;
    }

    for (i = 0; i < NUM_MEMSPACES; i++) {
        monitor_cpu_type_supported[i] = NULL;
    }
    /* We should really be told what CPUs are supported by each memspace, but that will
     * require a bunch of changes, so for now we detect it based on the available registers. */
    find_supported_monitor_cpu_types(&monitor_cpu_type_supported[e_comp_space], maincpu_interface_init);

    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        find_supported_monitor_cpu_types(&monitor_cpu_type_supported[monitor_diskspace_mem(dnr)],
                                         drive_interface_init[dnr]);
    }

    /* Build array of pointers to monitor_cpu_type structs */
    monitor_cpu_for_memspace[e_comp_space] =
        monitor_cpu_type_supported[e_comp_space]->monitor_cpu_type_p;
    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        monitor_cpu_for_memspace[monitor_diskspace_mem(dnr)] =
            monitor_cpu_type_supported[monitor_diskspace_mem(dnr)]->monitor_cpu_type_p;
    }
    /* Safety precaution */
    monitor_cpu_for_memspace[e_default_space] = monitor_cpu_for_memspace[e_comp_space];

    watch_load_occurred = false;
    watch_store_occurred = false;

    for (i = 1; i < NUM_MEMSPACES; i++) {
        dot_addr[i] = new_addr(e_default_space + i, 0);
        watch_load_count[i] = 0;
        watch_store_count[i] = 0;
        monitor_mask[i] = MI_NONE;
        monitor_labels[i].name_list = NULL;
        for (j = 0; j < HASH_ARRAY_SIZE; j++) {
            monitor_labels[i].addr_hash_table[j] = NULL;
        }
    }

    default_memspace = e_comp_space;

    asm_mode_addr = BAD_ADDR;

    mon_interfaces[e_comp_space] = maincpu_interface_init;

    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        mon_interfaces[monitor_diskspace_mem(dnr)] = drive_interface_init[dnr];
    }

    mon_memmap_init();

    /* set the current bank to the CPU bank. we need to do this here since this may not be bank 0 */
    if (mon_interfaces[e_comp_space]->mem_bank_from_name != NULL) {
        mon_interfaces[e_comp_space]->current_bank = mon_interfaces[e_comp_space]->mem_bank_from_name("cpu");
    } else {
        mon_interfaces[e_comp_space]->current_bank = 0;
    }

    if (init_break_mode == ON_EXECUTE) {
        /* Create the -initbreak execute address breakpoint */
        if (init_break_address >= 0 && init_break_address < 65536) {
            mon_breakpoint_add_checkpoint((uint16_t)init_break_address, BAD_ADDR,
                    true, e_exec, false, true);
        }
#ifndef TRAP_INITBREAK
    } else if (init_break_mode == ON_RESET) {
        mon_breakpoint_add_checkpoint((uint16_t)0, (uint16_t)0xffff,
                true, e_exec, true, false);
        exit_mon = 0;
#endif
    }
}

void monitor_shutdown(void)
{
    monitor_cpu_type_list_t *list, *list_next;
    supported_cpu_type_list_t *slist, *slist_next;
    int i;

    if (inside_monitor) {
        /* Can happen if VICE thread exited while monitor open */
        monitor_close(false);
    }

    if (last_cmd) {
        lib_free(last_cmd);
        last_cmd = NULL;
    }

    mon_log_file_close();

    list = monitor_cpu_type_list;

    while (list != NULL) {
        list_next = list->next_monitor_cpu_type;
        montor_list_destroy(list);
        list = list_next;
    }
    for (i = 0; i < NUM_MEMSPACES; i++) {
        slist = monitor_cpu_type_supported[i];
        while (slist != NULL) {
            slist_next = slist->next;
            lib_free(slist);
            slist = slist_next;
        }
    }

    mon_memmap_shutdown();

    while (playback_fp_stack_size) {
        playback_end_file();
    }

    lib_free(playback_filename_stack);
    lib_free(playback_fp_stack);

    playback_filename_stack = NULL;
    playback_fp_stack = NULL;
    playback_fp_stack_size = 0;
    playback_fp_stack_size_max = 0;
}

static int monitor_set_initial_breakpoint(const char *param, void *extra_param)
{
    long val;
    char *end;

    /* -initbreak <address> */

    errno = 0;
    val = strtoul(param, &end, 0);

    if (errno == 0 && end != param && *end == '\0') {
        if (val >= 0 && val < 65536) {
            init_break_mode = ON_EXECUTE;
            init_break_address = (int)val;

            return 0;
        }
    }

    /* -initbreak reset */

    if (strcmp(param, "reset") == 0) {
        init_break_mode = ON_RESET;
        return 0;
    }

    /* -initbreak ready */

    if (strcmp(param, "ready") == 0) {
        init_break_mode = ON_READY;
        return 0;
    }

    return -1;
}

static int keep_monitor_open = 0;

#ifdef ARCHDEP_SEPERATE_MONITOR_WINDOW
static int set_keep_monitor_open(int val, void *param)
{
    keep_monitor_open = val ? 1 : 0;

    return 0;
}
#endif

static int refresh_on_break = 0;

static int set_refresh_on_break(int val, void *param)
{
    refresh_on_break = val ? 1 : 0;

    return 0;
}

static char *monitorlogfilename = NULL;
static int monitorlogenabled = 0;

static int set_monitor_log_filename(const char *val, void *param)
{
    util_string_set(&monitorlogfilename, val);
    if (monitorlogenabled) {
        mon_log_file_close();
        mon_log_file_open(monitorlogfilename);
    }
    return 0;
}

static int set_monitor_log_enabled(int val, void *param)
{
    int old = monitorlogenabled;
    monitorlogenabled = val ? 1 : 0;

    if (!old && monitorlogenabled) {
        mon_log_file_open(monitorlogfilename);
    }
    if (old && !monitorlogenabled) {
        mon_log_file_close();
    }
    return 0;
}

#ifdef FEATURE_CPUMEMHISTORY
static int monitorchislines = 0;
static int set_monitor_chis_lines(int val, void *param)
{
    /* 10 lines minimum */
    if (val < 10) {
        val = 10;
    }
    monitorchislines = val;
    return monitor_cpuhistory_allocate(val);
}
#endif

static int monitorscrollbacklines = 0;
static int set_monitor_scrollback_lines(int val, void *param)
{
    /* -1 means no limit */
    if (val < -1) {
        val = -1;
    }
    monitorscrollbacklines = val;
    return 0;
}

static const resource_string_t resources_string[] = {
    { "MonitorLogFileName", "monitor.log", RES_EVENT_NO, NULL,
      &monitorlogfilename, set_monitor_log_filename, (void *)0 },
    RESOURCE_STRING_LIST_END
};

static const resource_int_t resources_int[] = {
#ifdef ARCHDEP_SEPERATE_MONITOR_WINDOW
    { "KeepMonitorOpen", 1, RES_EVENT_NO, NULL,
      &keep_monitor_open, set_keep_monitor_open, NULL },
#endif
    { "RefreshOnBreak", 1, RES_EVENT_NO, NULL,
      &refresh_on_break, set_refresh_on_break, NULL },
    { "MonitorLogEnabled", 0, RES_EVENT_NO, NULL,
      &monitorlogenabled, set_monitor_log_enabled, NULL },
#ifdef FEATURE_CPUMEMHISTORY
    { "MonitorChisLines", 8192, RES_EVENT_NO, NULL,
      &monitorchislines, set_monitor_chis_lines, NULL },
#endif
    { "MonitorScrollbackLines", 8192, RES_EVENT_NO, NULL,
      &monitorscrollbacklines, set_monitor_scrollback_lines, NULL },
    RESOURCE_INT_LIST_END
};

int monitor_resources_init(void)
{
    if (resources_register_string(resources_string) < 0) {
        return -1;
    }
    return resources_register_int(resources_int);
}


void monitor_resources_shutdown(void)
{
    if (monitorlogfilename != NULL) {
        lib_free(monitorlogfilename);
        monitorlogfilename = NULL;
    }
}


static const cmdline_option_t cmdline_options[] =
{
    { "-moncommands", CALL_FUNCTION, CMDLINE_ATTRIB_NEED_ARGS,
      monitor_set_moncommands_file, NULL, NULL, NULL,
      "<Name>", "Execute monitor commands from file" },
    { "-monlogname", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "MonitorLogFileName", NULL,
      "<Name>", "Set name of monitor log file" },
    { "-monlog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "MonitorLogEnabled", (resource_value_t)1,
      NULL, "Enable logging monitor output to a file" },
    { "+monlog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "MonitorLogEnabled", (resource_value_t)0,
      NULL, "Disable logging monitor output to a file" },
    { "-initbreak", CALL_FUNCTION, CMDLINE_ATTRIB_NEED_ARGS,
      monitor_set_initial_breakpoint, NULL, NULL, NULL,
      "<value>", "Set an initial breakpoint for the monitor: <address>, ready, or reset" },
#ifdef ARCHDEP_SEPERATE_MONITOR_WINDOW
    { "-keepmonopen", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "KeepMonitorOpen", (resource_value_t)1,
      NULL, "Keep the monitor open" },
    { "+keepmonopen", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "KeepMonitorOpen", (resource_value_t)0,
      NULL, "Do not keep the monitor open" },
    { "-refreshonbreak", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "RefreshOnBreak", (resource_value_t)1,
      NULL, "Refresh display after monitor command" },
    { "+refreshonbreak", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
      NULL, NULL, "RefreshOnBreak", (resource_value_t)0,
      NULL, "Do not refresh display after monitor command" },
#endif
    { "-monscrollbacklines", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "MonitorScrollbackLines", NULL,
      "<value>", "Set number of lines to keep in the monitor scrollback buffer" },
#ifdef FEATURE_CPUMEMHISTORY
    { "-monchislines", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
      NULL, NULL, "MonitorChisLines", NULL,
      "<value>", "Set number of lines to keep in the cpu history" },
#endif
    CMDLINE_LIST_END
};

int monitor_cmdline_options_init(void)
{
    mon_cart_cmd.cartridge_attach_image = NULL;
    mon_cart_cmd.cartridge_detach_image = NULL;
    mon_cart_cmd.cartridge_trigger_freeze = NULL;
    mon_cart_cmd.cartridge_trigger_freeze_nmi_only = NULL;

    return cmdline_register_options(cmdline_options);
}

monitor_interface_t *monitor_interface_new(void)
{
    return (monitor_interface_t *)lib_calloc(sizeof(monitor_interface_t), 1);
}

void monitor_interface_destroy(monitor_interface_t *monitor_interface)
{
    lib_free(monitor_interface);
}

void mon_start_assemble_mode(MON_ADDR addr, char *asm_line)
{
    asm_mode = 1;

    mon_evaluate_default_addr(&addr);
    asm_mode_addr = addr;
}

/* ------------------------------------------------------------------------- */

/* Memory.  */


/** \brief  Display screen memory
 *
 * Display (current) screen memory, converting PETSCII to ASCII.
 *
 * The lex/yuck stuff will pass in either -1 when no arg has been given or an
 * address.
 *
 * \param[in]   address address of screen memory, -1 to show current screen
 *
 * \note    Currently doesn't show the addresses in the SDL monitor, since the
 *          SDL monitor is only 40 columns
 */
void mon_display_screen(long addr)
{
    uint16_t base;
    uint8_t rows, cols;
    unsigned int r, c;
    int bank;
    /* FIXME: this should really be handled by the UI instead, ie the UI should
              always just give us valid numbers */
    static int last_known_xres = 40;
    if (console_log) {
        last_known_xres = console_log->console_xres;
    }
#if 0
    printf("Address = %ld\n", addr);
#endif
    mem_get_screen_parameter(&base, &rows, &cols, &bank);
#if 0
    printf("base: $%04x, rows: $%02x, cols: $%02x, bank: %d\n",
            base, rows, cols, bank);
#endif
    /* hard core: change address vars */
    if (addr >= 0) {
        bank = mon_interfaces[e_comp_space]->current_bank;
        base = addr;
    }


    /* We need something like bankname = something(e_comp_space, bank) here */
    mon_out("Displaying %dx%d screen at $%04x:\n", cols, rows, base);

    for (r = 0; r < rows; r++) {
        if ((cols + 9) < last_known_xres) {
            mon_out("*C:%04x  ", base);
        }
        for (c = 0; c < cols; c++) {
            uint8_t data;

            /* Not sure this really neads to use mon_get_mem_val_ex()
               Do we want monitor sidefx in a function that's *supposed*
               to just read from screen memory? */
            data = mon_get_mem_val_ex_nosfx(e_comp_space, bank, (uint16_t)ADDR_LIMIT(base++));
            mon_scrcode_out(1, "%c", data);
        }
        mon_out("\n");
    }
}

/*
    display io regs

    if addr = 0 display full list, no details
    if addr = 1 display full list, with details

    for other addr display full details for respective device
*/
void mon_display_io_regs(MON_ADDR addr)
{
    mem_ioreg_list_t *mem_ioreg_list_base;
    unsigned int n;
    MON_ADDR start, end;
    int newbank = 0;
    int currbank = mon_interfaces[default_memspace]->current_bank;

    if (mon_interfaces[default_memspace]->mem_bank_list) {
        newbank = mon_interfaces[default_memspace]->mem_bank_from_name("io");
    }

    if (newbank >= 0) {
        mon_interfaces[default_memspace]->current_bank = newbank;
    }

    mem_ioreg_list_base
        = mon_interfaces[default_memspace]->mem_ioreg_list_get(
        mon_interfaces[default_memspace]->context);
    n = 0;

    if (mem_ioreg_list_base) {
        while (1) {
            start = mem_ioreg_list_base[n].start;
            end = mem_ioreg_list_base[n].end;

            if (!((addr == 0) && (mem_ioreg_list_base[n].mirror_mode == IO_MIRROR_OTHER))) {
                if ((addr < 2) || ((addr >= start) && (addr <= end))) {
                    if ((addr == 1) && (n > 0)) {
                        mon_out("\n");
                    }
                    start = new_addr(default_memspace, start);
                    end = new_addr(default_memspace, end);
                    mon_out("%s:\n", mem_ioreg_list_base[n].name);
                    mon_memory_display(e_hexadecimal, start, end, DF_PETSCII);

                    if (addr > 0) {
                        if (mem_ioreg_list_base[n].dump) {
                            mon_out("\n");
                            if (mem_ioreg_list_base[n].dump(mem_ioreg_list_base[n].context,
                                    (uint16_t)(addr_location(start))) < 0) {
                                mon_out("No details available.\n");
                            }
                        } else {
                            mon_out("No details available.\n");
                        }
                    }
                }
            }

            if (mem_ioreg_list_base[n].next == 0) {
                break;
            }
            n++;
        }
    } else {
        mon_out("No I/O regs available\n");
    }

    mon_interfaces[default_memspace]->current_bank = currbank;
    lib_free(mem_ioreg_list_base);
}

void mon_ioreg_add_list(mem_ioreg_list_t **list, const char *name,
                        int start_, int end_, void *dump, void *context, int mirror_mode)
{
    mem_ioreg_list_t *base;
    unsigned int n;
    uint16_t start = start_ & 0xFFFFu;
    uint16_t end = end_ & 0xFFFFu;

    assert(start == start_);
    assert(end == end_);

    base = *list;
    n = 0;

    while (base != NULL) {
        n++;
        if (base[n - 1].next == 0) {
            break;
        }
    }

    base = lib_realloc(base, sizeof(mem_ioreg_list_t) * (n + 1));

    if (n > 0) {
        base[n - 1].next = 1;
    }

    base[n].name = name;
    base[n].start = start;
    base[n].end = end;
    base[n].dump = dump;
    base[n].context = context;
    base[n].mirror_mode = mirror_mode;
    base[n].next = 0;

    *list = base;
}


/* *** FILE COMMANDS *** */

void mon_change_dir(const char *path)
{
    if (path != NULL && path[0] == '~' && path[1] == '\0') {
        path = archdep_home_path();
    }
    if (archdep_chdir(path) != 0) {
        mon_out("Cannot change to directory: '%s'\n", path);
    } else {
        mon_out("Changing to directory: '%s'\n", path);
    }
}


void mon_make_dir(const char *path)
{
    if (archdep_mkdir(path, 0755) < 0) {
        mon_out("Cannot create directory '%s': %d: %s\n",
                path, errno, strerror(errno));
    } else {
        mon_out("Created directory '%s'\n", path);
    }
}


void mon_remove_dir(const char *path)
{
    if (archdep_rmdir(path) < 0) {
        mon_out("Cannot remove directory '%s': %d: %s\n",
                path, errno, strerror(errno));
    } else {
        mon_out("Removed directory '%s'\n", path);
    }
}




void mon_save_symbols(MEMSPACE mem, const char *filename)
{
    FILE *fp;
    symbol_entry_t *sym_ptr;

    if (NULL == (fp = fopen(filename, MODE_WRITE))) {
        mon_out("Saving for `%s' failed.\n", filename);
        return;
    }

    mon_out("Saving symbol table to `%s'...\n", filename);

    /* FIXME: Write out all memspaces? */
    if (mem == e_default_space) {
        mem = default_memspace;
    }

    sym_ptr = monitor_labels[mem].name_list;

    while (sym_ptr) {
        fprintf(fp, "al %s:%04x %s\n", mon_memspace_string[mem], sym_ptr->addr,
                sym_ptr->name);
        sym_ptr = sym_ptr->next;
    }

    fclose(fp);
}


/* *** COMMAND FILES *** */


void mon_record_commands(char *filename)
{
    if (recording) {
        mon_out("Recording already in progress. Use 'stop' to end recording.\n");
        return;
    }

    recording_name = filename;

    if (NULL == (recording_fp = fopen(recording_name, MODE_WRITE))) {
        mon_out("Cannot create `%s'.\n", recording_name);
        return;
    }

    setbuf(recording_fp, NULL);

    recording = true;
}

void mon_end_recording(void)
{
    if (!recording) {
        mon_out("No file is currently being recorded.\n");
        return;
    }

    fclose(recording_fp);
    mon_out("Closed file %s.\n", recording_name);
    recording = false;
}


static int monitor_set_moncommands_file(const char *filename, void *extra_param)
{
    if (mon_playback_commands(filename, false) != 0) {
        return -1;
    }

    /*
     * Default for -moncommands is to execute immediately on launch.
     * Override it if it hasn't been changed.
     */

    if (init_break_mode == NONE) {
        init_break_mode = ON_RESET;
    }

    /*
     * We're executing a -moncommands script at some point, so return
     * to emulation when it completes.
     */

    playback_exit_after_all_playback = true;

    return 0;
}


int mon_playback_commands(const char *filename, bool interrupt_current_playback)
{
    FILE *fp;

    log_message(LOG_DEFAULT, "Opening monitor command playback file: %s", filename);

    if (playback_fp_stack_size == playback_fp_stack_size_max) {
        /*
         * Need to grow the playback FP stack. But look out for insane playback include depth.
         */
        if (playback_fp_stack_size_max >= PLAYBACK_MAX_FP_DEPTH) {
            log_error(LOG_DEFAULT, "Max level of playback file depth %d reached, exiting", playback_fp_stack_size_max);
            archdep_vice_exit(1);
        }

        playback_fp_stack_size_max += 1;
        playback_fp_stack       = lib_realloc(playback_fp_stack,       playback_fp_stack_size_max * sizeof(FILE *));
        playback_filename_stack = lib_realloc(playback_filename_stack, playback_fp_stack_size_max * sizeof(char *));
    }

    fp = fopen(filename, MODE_READ_TEXT);

    if (fp == NULL) {
        fp = sysfile_open(filename, NULL, NULL, MODE_READ_TEXT);
    }

    if (fp == NULL) {
        log_error(LOG_DEFAULT, "Failed to open playback file: '%s'", filename);
        mon_out("Cannot open '%s'.\n", filename);
        return -1;
    }

    if (interrupt_current_playback || playback_fp_stack_size == 0) {
        /*
         * Place the new file at the top of the stack and set the current playback fp,
         * which means that the commands within this file will be be next commands to execute.
         */

        playback_fp = fp;

        playback_fp_stack[playback_fp_stack_size] = fp;
        playback_filename_stack[playback_fp_stack_size] = lib_strdup(filename);
    } else {
        /*
         * Place the new file at the bottom of the stack, which means that the commands
         * within will execute after all queued playback files have finished. If no
         * playback is currently active, start with this file.
         */

        memmove(playback_fp_stack + 1,       playback_fp_stack,       playback_fp_stack_size * sizeof(char *));
        memmove(playback_filename_stack + 1, playback_filename_stack, playback_fp_stack_size * sizeof(FILE *));

        playback_fp_stack[0] = fp;
        playback_filename_stack[0] = lib_strdup(filename);
    }

    playback_fp_stack_size++;

    return 0;
}

static void playback_end_file(void)
{
    fclose(playback_fp);

    playback_fp_stack_size--;

    log_message(LOG_DEFAULT, "Closed monitor command playback file: %s", playback_filename_stack[playback_fp_stack_size]);
    lib_free(playback_filename_stack[playback_fp_stack_size]);

    playback_filename_stack[playback_fp_stack_size] = NULL;
    playback_fp_stack[playback_fp_stack_size] = NULL;

    if (playback_fp_stack_size) {
        /* Return to playing back the previous file */
        playback_fp = playback_fp_stack[playback_fp_stack_size - 1];
        return;
    }

    /* Playback of all files is complete */
    playback_fp = NULL;

    /* Should we return to emulation? (Yes if -moncommands arg used) */
    if (playback_exit_after_all_playback) {
        playback_exit_after_all_playback = false;
        mon_exit();
    }
}

static void playback_next_command(void)
{
    char line[1024];
    char *command;

    if (fgets(line, sizeof(line), playback_fp) == NULL) {
        /* Reached the end of the file */
        playback_end_file();

        if (playback_fp) {
            playback_next_command();
        }
        return;
    }

    line[strlen(line) - 1] = '\0';
    command = lib_strdup_trimmed(line);

    log_message(LOG_DEFAULT, "Monitor playback command: %s", command);
    parse_and_execute_line(command);

    lib_free(command);
}


/* *** SYMBOL TABLE *** */


static void free_symbol_table(MEMSPACE mem)
{
    symbol_entry_t *sym_ptr, *temp;
    int i;

    /* Remove name list */
    sym_ptr = monitor_labels[mem].name_list;
    while (sym_ptr) {
        /* Name memory is freed below. */
        temp = sym_ptr;
        sym_ptr = sym_ptr->next;
        lib_free(temp);
    }

    /* Remove address hash table */
    for (i = 0; i < HASH_ARRAY_SIZE; i++) {
        sym_ptr = monitor_labels[mem].addr_hash_table[i];
        while (sym_ptr) {
            lib_free (sym_ptr->name);
            temp = sym_ptr;
            sym_ptr = sym_ptr->next;
            lib_free(temp);
        }
    }
}

char *mon_symbol_table_lookup_name(MEMSPACE mem, uint16_t addr)
{
    symbol_entry_t *sym_ptr;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    sym_ptr = monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)];
    while (sym_ptr) {
        if (addr == sym_ptr->addr) {
            return sym_ptr->name;
        }
        sym_ptr = sym_ptr->next;
    }

    return NULL;
}

/* look up a symbol in the given memspace, returns address or -1 on error */
int mon_symbol_table_lookup_addr(MEMSPACE mem, char *name)
{
    symbol_entry_t *sym_ptr;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    /* this allows for .REGISTER to be used in all commands to refer to the
       current value of a register */
    if ((name[0] == '.') && mon_register_name_valid(mem, &name[1])) {
        return mon_register_name_to_value(mem, &name[1]);
    }

    sym_ptr = monitor_labels[mem].name_list;
    while (sym_ptr) {
        if (strcmp(sym_ptr->name, name) == 0) {
            return sym_ptr->addr;
        }
        sym_ptr = sym_ptr->next;
    }

    return -1;
}

char * mon_prepend_dot_to_name(char *name)
{
    char *s = malloc(strlen(name) + 2);
    strcpy(s, ".");
    strcat(s, name);
    lib_free(name);
    return s;
}

void mon_add_name_to_symbol_table(MON_ADDR addr, char *name)
{
    symbol_entry_t *sym_ptr;
    char *old_name;
    int old_addr;
    MEMSPACE mem = addr_memspace(addr);
    uint16_t loc = addr_location(addr);
    int silent = (playback_fp != NULL); /* suppress warnings when playing back label file */

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    /* .REGISTER can be used in all commands to refer to the current value of a
       register, meaning it can not be used as a regular label */
    if ((name[0] == '.') && mon_register_name_valid(mem, &name[1])) {
        mon_out("Error: %s is a reserved label.\n", name);
        return;
    }

    old_name = mon_symbol_table_lookup_name(mem, loc);
    old_addr = mon_symbol_table_lookup_addr(mem, name);
    if ((old_name && (MON_ADDR)addr_location(old_addr) != addr) && (!silent)) {
        mon_out("Warning: label(s) for address $%04x already exist.\n", loc);
    }
    if (old_addr >= 0) {
        if ((old_addr != loc) && (!silent)) {
            mon_out("Changing address of label %s from $%04x to $%04x\n",
                    name, (unsigned int)old_addr, loc);
        }
        mon_remove_name_from_symbol_table(mem, name);
    }

    /* Add name to name list */
    sym_ptr = lib_malloc(sizeof(symbol_entry_t));
    sym_ptr->name = name;
    sym_ptr->addr = loc;

    sym_ptr->next = monitor_labels[mem].name_list;
    monitor_labels[mem].name_list = sym_ptr;

    /* Add address to hash table */
    sym_ptr = lib_malloc(sizeof(symbol_entry_t));
    sym_ptr->name = name;
    sym_ptr->addr = addr;

    sym_ptr->next = monitor_labels[mem].addr_hash_table[HASH_ADDR(loc)];
    monitor_labels[mem].addr_hash_table[HASH_ADDR(loc)] = sym_ptr;
}

void mon_remove_name_from_symbol_table(MEMSPACE mem, char *name)
{
    int addr;
    symbol_entry_t *sym_ptr, *prev_ptr;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    if (name == NULL) {
        /* FIXME - prompt user */
        free_symbol_table(mem);
        return;
    }

    if ((addr = mon_symbol_table_lookup_addr(mem, name)) < 0) {
        mon_out("Symbol %s not found.\n", name);
        return;
    }

    /* Remove entry in name list */
    sym_ptr = monitor_labels[mem].name_list;
    prev_ptr = NULL;
    while (sym_ptr) {
        if (strcmp(sym_ptr->name, name) == 0) {
            /* Name memory is freed below. */
            addr = sym_ptr->addr;
            if (prev_ptr) {
                prev_ptr->next = sym_ptr->next;
            } else {
                monitor_labels[mem].name_list = sym_ptr->next;
            }
            lib_free(sym_ptr);
            break;
        }
        prev_ptr = sym_ptr;
        sym_ptr = sym_ptr->next;
    }

    /* Remove entry in address hash table */
    sym_ptr = monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)];
    prev_ptr = NULL;
    while (sym_ptr) {
        if ((addr == sym_ptr->addr) && !strcmp(sym_ptr->name, name)) {
            lib_free(sym_ptr->name);
            if (prev_ptr) {
                prev_ptr->next = sym_ptr->next;
            } else {
                monitor_labels[mem].addr_hash_table[HASH_ADDR(addr)] = sym_ptr->next;
            }
            lib_free(sym_ptr);
            return;
        }
        prev_ptr = sym_ptr;
        sym_ptr = sym_ptr->next;
    }
}

void mon_print_symbol_table(MEMSPACE mem)
{
    symbol_entry_t *sym_ptr;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    sym_ptr = monitor_labels[mem].name_list;
    while (sym_ptr) {
        mon_out("$%04x %s\n", sym_ptr->addr, sym_ptr->name);
        sym_ptr = sym_ptr->next;
    }
}

void mon_clear_symbol_table(MEMSPACE mem)
{
    int i;

    if (mem == e_default_space) {
        mem = default_memspace;
    }

    free_symbol_table(mem);
    monitor_labels[mem].name_list = NULL;

    for (i = 0; i < HASH_ARRAY_SIZE; i++) {
        monitor_labels[mem].addr_hash_table[i] = NULL;
    }
}


/* *** INSTRUCTION COMMANDS *** */


void mon_instructions_step(int count)
{
    if (count >= 0) {
        mon_out("Stepping through the next %d instruction(s).\n", count);
    }
    instruction_count = (count >= 0) ? count : 1;
    wait_for_return_level = 0;
    skip_jsrs = false;
    exit_mon = 1;

    mon_console_suspend_on_leaving = 0;

    monitor_mask[default_memspace] |= MI_STEP;
    interrupt_monitor_trap_on(mon_interfaces[default_memspace]->int_status);
}

void mon_instructions_next(int count)
{
    if (count >= 0) {
        mon_out("Nexting through the next %d instruction(s).\n", count);
        instruction_count = count;
    }
    else {
        instruction_count = 1;
    }
    wait_for_return_level = (int)((MONITOR_GET_OPCODE(default_memspace) == OP_JSR) ? 1 : 0);
    skip_jsrs = true;
    exit_mon = 1;

    mon_console_suspend_on_leaving = 0;

    monitor_mask[default_memspace] |= MI_STEP;
    interrupt_monitor_trap_on(mon_interfaces[default_memspace]->int_status);
}

void mon_instruction_return(void)
{
    instruction_count = 1;
    /* clear abuse of the ?: operator: */
    wait_for_return_level = (int)(
            (MONITOR_GET_OPCODE(default_memspace) == OP_RTS
             || MONITOR_GET_OPCODE(default_memspace) == OP_RTI) ?
            0 : (MONITOR_GET_OPCODE(default_memspace) == OP_JSR) ? 2 : 1);
    skip_jsrs = true;
    exit_mon = 1;

    monitor_mask[default_memspace] |= MI_STEP;
    interrupt_monitor_trap_on(mon_interfaces[default_memspace]->int_status);
}

void mon_stack_up(int count)
{
    mon_out("Going up %d stack frame(s).\n", (count >= 0) ? count : 1);
}

void mon_stack_down(int count)
{
    mon_out("Going down %d stack frame(s).\n", (count >= 0) ? count : 1);
}


/* *** CONDITIONAL EXPRESSIONS *** */


void mon_print_conditional(cond_node_t *cnode)
{
    /* Do an in-order traversal of the tree */
    if (cnode->is_parenthized) {
        mon_out("( ");
    }

    if (cnode->operation != e_INV) {
        if (!(cnode->child1 && cnode->child2)) {
            log_error(LOG_DEFAULT, "No conditional!");
            return;
        }
        mon_print_conditional(cnode->child1);
        mon_out(" %s ", cond_op_string[cnode->operation]);
        mon_print_conditional(cnode->child2);
    } else {
        if (cnode->is_reg) {
            mon_out("%s", register_string[reg_regid(cnode->reg_num)]);
        }
        else if (cnode->banknum >= 0) {
            if (cnode->child1) {
                mon_out("@%s:(",
                        mon_get_bank_name_for_bank(default_memspace,cnode->banknum));
                mon_print_conditional(cnode->child1);
                mon_out(")");
            } else {
                mon_out("@%s:$%04x",
                        mon_get_bank_name_for_bank(default_memspace,cnode->banknum),
                        (unsigned int)cnode->value);
            }
        } else {
            mon_out("$%02x", (unsigned int)cnode->value);
        }
    }

    if (cnode->is_parenthized) {
        mon_out(" )");
    }
}


int mon_evaluate_conditional(cond_node_t *cnode)
{
    /* Do a post-order traversal of the tree */
    if (cnode->operation != e_INV) {
        int value_1, value_2;

        if (!(cnode->child1 && cnode->child2)) {
            log_error(LOG_DEFAULT, "No conditional!");
            return 0;
        }
        value_1 = mon_evaluate_conditional(cnode->child1);
        value_2 = mon_evaluate_conditional(cnode->child2);

        switch (cnode->operation) {
            case e_EQU:
                cnode->value = (value_1 == value_2);
                break;
            case e_NEQ:
                cnode->value = (value_1 != value_2);
                break;
            case e_GT:
                cnode->value = (value_1 > value_2);
                break;
            case e_LT:
                cnode->value = (value_1 < value_2);
                break;
            case e_GTE:
                cnode->value = (value_1 >= value_2);
                break;
            case e_LTE:
                cnode->value = (value_1 <= value_2);
                break;
            case e_LOGICAL_AND:
                cnode->value = (value_1 && value_2);
                break;
            case e_LOGICAL_OR:
                cnode->value = (value_1 || value_2);
                break;
            case e_ADD:
                cnode->value = (value_1 + value_2);
                break;
            case e_SUB:
                cnode->value = (value_1 - value_2);
                break;
            case e_MUL:
                cnode->value = (value_1 * value_2);
                break;
            case e_DIV:
                if (value_2 == 0) {
                    log_error(LOG_DEFAULT, "Division by zero in conditional\n");
                    return 0;
                }
                cnode->value = (value_1 / value_2);
                break;
            case e_BINARY_AND:
                cnode->value = (value_1 & value_2);
                break;
            case e_BINARY_OR:
                cnode->value = (value_1 | value_2);
                break;
            default:
                log_error(LOG_DEFAULT, "Unexpected conditional operator: %d\n",
                          cnode->operation);
                return 0;
        }
    } else {
        if (cnode->is_reg && (reg_regid(cnode->reg_num) == e_Rasterline) ) {
            unsigned int line, cycle;
            int half_cycle;
            mon_interfaces[e_comp_space]->get_line_cycle(&line, &cycle, &half_cycle);
            cnode->value = line;
        } else if (cnode->is_reg && (reg_regid(cnode->reg_num) == e_Cycle) ) {
            unsigned int line, cycle;
            int half_cycle;
            mon_interfaces[e_comp_space]->get_line_cycle(&line, &cycle, &half_cycle);
            cnode->value = cycle;
        } else if (cnode->is_reg) {
            cnode->value = (monitor_cpu_for_memspace[reg_memspace(cnode->reg_num)]->mon_register_get_val)
                               (reg_memspace(cnode->reg_num),
                               reg_regid(cnode->reg_num));
        } else if (cnode->banknum >= 0) {
            MEMSPACE src_mem = e_comp_space;
            uint16_t start;
            if (cnode->child1 != NULL) {
                start = mon_evaluate_conditional(cnode->child1);
            } else {
                start = addr_location(cnode->value);
            }
            uint8_t byte1;
            int old_sidefx = sidefx; /*we need to store current value*/
            sidefx = 0; /*make sure we peek when doing the break point, otherwise weird stuff will happen*/

            byte1 = mon_get_mem_val_ex(src_mem, cnode->banknum, start);

            sidefx = old_sidefx; /*restore value*/
            return byte1;
        }
    }

    return cnode->value;
}


void mon_delete_conditional(cond_node_t *cnode)
{
    if (!cnode) {
        return;
    }

    if (cnode->child1) {
        mon_delete_conditional(cnode->child1);
    }

    if (cnode->child2) {
        mon_delete_conditional(cnode->child2);
    }

    lib_free(cnode);
}


/* *** SNAPSHOTS *** */


int mon_write_snapshot(const char *name, int save_roms, int save_disks, int even_mode)
{
    return machine_write_snapshot(name, save_roms, save_disks, even_mode);
}

int mon_read_snapshot(const char *name, int even_mode)
{
    int ret;

    ret = machine_read_snapshot(name, even_mode);

    /* Reset the current address */
    dot_addr[e_comp_space] = new_addr(e_comp_space, ((uint16_t)((monitor_cpu_for_memspace[e_comp_space]->mon_register_get_val)(e_comp_space, e_PC))));

    return ret;
}


/* *** WATCHPOINTS *** */


void monitor_watch_push_load_addr(uint16_t addr, MEMSPACE mem)
{
    if (inside_monitor) {
        return;
    }

    if (watch_load_count[mem] == MONITOR_MAX_CHECKPOINTS) {
        return;
    }

    watch_load_occurred = true;
    watch_load_array[watch_load_count[mem]][mem] = addr;
    watch_load_count[mem]++;
}

void monitor_watch_push_store_addr(uint16_t addr, MEMSPACE mem)
{
    if (inside_monitor) {
        return;
    }

    if (watch_store_count[mem] == MONITOR_MAX_CHECKPOINTS) {
        return;
    }

    watch_store_occurred = true;
    watch_store_array[watch_store_count[mem]][mem] = addr;
    watch_store_count[mem]++;
}

static bool watchpoints_check_loads(MEMSPACE mem, unsigned int lastpc, unsigned int pc)
{
    bool trap = false;
    unsigned count, n;
    uint16_t addr = 0;

    count = watch_load_count[mem];
    /* check from index 0 upwards, so when more than one checkpoint was triggered
       they get printed in the right order */
    for (n = 0; n < count; n++) {
        addr = watch_load_array[n][mem];
        if (mon_breakpoint_check_checkpoint(mem, addr, lastpc, e_load)) {
            trap = true;
        }
    }
    watch_load_count[mem] = 0;
    return trap;
}

static bool watchpoints_check_stores(MEMSPACE mem, unsigned int lastpc, unsigned int pc)
{
    bool trap = false;
    unsigned count, n;
    uint16_t addr = 0;

    count = watch_store_count[mem];
    /* check from index 0 upwards, so when more than one checkpoint was triggered
       they get printed in the right order */
    for (n = 0; n < count; n++) {
        addr = watch_store_array[n][mem];
        if (mon_breakpoint_check_checkpoint(mem, addr, lastpc, e_store)) {
            trap = true;
        }
    }
    watch_store_count[mem] = 0;
    return trap;
}


/* *** CPU INTERFACES *** */

/* called by cpu core */
void monitor_check_icount(uint16_t pc)
{
    if (!instruction_count) {
        return;
    }

    if (wait_for_return_level == 0) {
        instruction_count--;
    }

    if (skip_jsrs == true) {
        /*
            maintain the return level while "trace over"

            - if the current address is the start of a trap, the respective opcode
              is not actually executed and thus is ignored.
        */
        if ((default_memspace != e_comp_space) || (traps_checkaddr(pc) == 0)) {
            if (MONITOR_GET_OPCODE(default_memspace) == OP_JSR) {
                wait_for_return_level++;
            }
            if (MONITOR_GET_OPCODE(default_memspace) == OP_RTS) {
                wait_for_return_level--;
            }
            if (MONITOR_GET_OPCODE(default_memspace) == OP_RTI) {
                wait_for_return_level--;
            }
            if (wait_for_return_level < 0) {
                wait_for_return_level = 0;
            }
        }
    }

    if (instruction_count != 0) {
        return;
    }

    if (monitor_mask[default_memspace] & MI_STEP) {
        monitor_mask[default_memspace] &= ~MI_STEP;
        disassemble_on_entry = 1;
    }
    if (!monitor_mask[default_memspace]) {
        interrupt_monitor_trap_off(mon_interfaces[default_memspace]->int_status);
    }

    monitor_startup(e_default_space);
}

/* called by cpu core */
void monitor_check_icount_interrupt(void)
{
    /* This is a helper for monitor_check_icount.
    It's called whenever a IRQ or NMI is executed
    and the monitor_mask[default_memspace] | MI_STEP is
    active, i.e., we're in the single step mode.   */

    if (instruction_count) {
        if (skip_jsrs == true) {
            wait_for_return_level++;
        }
    }
}

/* called by macro DO_INTERRUPT() in 6510(dtv)core.c
 * returns non-zero if breakpoint hit and monitor should be invoked
 */
int monitor_check_breakpoints(MEMSPACE mem, uint16_t addr)
{
    return mon_breakpoint_check_checkpoint(mem, addr, 0, e_exec); /* FIXME */
}

/* called by macro DO_INTERRUPT() in 6510(dtv)core.c */
void monitor_check_watchpoints(unsigned int lastpc, unsigned int pc)
{
    unsigned int dnr;

    if (watch_load_occurred) {
        if (watchpoints_check_loads(e_comp_space, lastpc, pc)) {
            monitor_startup(e_comp_space);
        }
        for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
            if (watchpoints_check_loads(monitor_diskspace_mem(dnr), lastpc, pc)) {
                monitor_startup(monitor_diskspace_mem(dnr));
            }
        }
        watch_load_occurred = false;
    }

    if (watch_store_occurred) {
        if (watchpoints_check_stores(e_comp_space, lastpc, pc)) {
            monitor_startup(e_comp_space);
        }
        for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
            if (watchpoints_check_stores(monitor_diskspace_mem(dnr), lastpc, pc)) {
                monitor_startup(monitor_diskspace_mem(dnr));
            }
        }
        watch_store_occurred = false;
    }
}

int monitor_diskspace_dnr(int mem)
{
    switch (mem) {
        case e_disk8_space:
            return 0;
        case e_disk9_space:
            return 1;
        case e_disk10_space:
            return 2;
        case e_disk11_space:
            return 3;
    }

    return -1;
}

int monitor_diskspace_mem(int dnr)
{
    switch (dnr) {
        case 0:
            return e_disk8_space;
        case 1:
            return e_disk9_space;
        case 2:
            return e_disk10_space;
        case 3:
            return e_disk11_space;
    }

    return 0;
}

void monitor_change_device(MEMSPACE mem)
{
    /* if no argument given, switch back to computer */
    if (mem == e_default_space) {
        mem = e_comp_space;
    }
    mon_out("Setting default device to `%s'\n", _mon_space_strings[(int) mem]);
    default_memspace = mem;
}

static void make_prompt(char *str)
{
    if (asm_mode) {
        sprintf(str, ".%04x  ", addr_location(asm_mode_addr));
    } else {
        sprintf(str, "(%s:$%04x) ",
                mon_memspace_string[default_memspace],
                addr_location(dot_addr[default_memspace]));
    }
}

void monitor_abort(void)
{
    mon_stop_output = 1;
}

#define TTY_COLUMNS 80
#define TTY_ROWS    25

static void monitor_open(void)
{
    supported_cpu_type_list_t *slist, *slist_next;
    monitor_cpu_type_t *monitor_cpu_type_p = NULL;
    unsigned int dnr;
    int i;

    mon_console_suspend_on_leaving = 1;
    mon_console_close_on_leaving = 0;

    if (console_mode) {
        /* Shitty hack. We should support the console size etc. */
        static console_t console_log_console = { TTY_COLUMNS, TTY_ROWS, 0, 0, NULL };
        console_log = &console_log_console;
    } else if (monitor_is_remote() || monitor_is_binary()) {
        static console_t console_log_remote = { TTY_COLUMNS, TTY_ROWS, 0, 0, NULL };
        console_log = &console_log_remote;
    } else {
        if (console_log) {
            console_log = uimon_window_resume();
        } else {
            /* We 'open' the monitor, but only display it if we're not in -moncommands playback mode */
            console_log = uimon_window_open(playback_fp == NULL);
            uimon_set_interface(mon_interfaces, NUM_MEMSPACES);
        }
    }

    if (console_log == NULL) {
        log_error(LOG_DEFAULT, "monitor_open: could not open monitor console.");
        exit_mon = 1;
        return;
    }

#ifdef FEATURE_CPUMEMHISTORY
    memmap_state |= MEMMAP_STATE_IN_MONITOR;
#endif
    inside_monitor = true;
    vsync_suspend_speed_eval();
    sound_suspend();

    uimon_notify_change();

    /* free the list of supported CPUs */
    for (i = 0; i < NUM_MEMSPACES; i++) {
        slist = monitor_cpu_type_supported[i];
        while (slist != NULL) {
            slist_next = slist->next;
            lib_free(slist);
            slist = slist_next;
        }
        monitor_cpu_type_supported[i] = NULL;
    }

    /* We should really be told what CPUs are supported by each memspace, but that will
     * require a bunch of changes, so for now we detect it based on the available registers. */
    find_supported_monitor_cpu_types(&monitor_cpu_type_supported[e_comp_space], mon_interfaces[e_comp_space]);

    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        find_supported_monitor_cpu_types(&monitor_cpu_type_supported[monitor_diskspace_mem(dnr)],
                                         mon_interfaces[monitor_diskspace_mem(dnr)]);
    }

    /* Build array of pointers to monitor_cpu_type structs */

    /* NOTE: We can't just init the struct(s) with the default CPU for each memspace, if
             we did that, the monitor will not be able to eg single step on any CPU that
             is not the first in the list. This loop makes sure the last active CPU is
             still active in the monitor. */
    for (i = 0; i < NUM_MEMSPACES; i++) {
        slist = monitor_cpu_type_supported[i];
        monitor_cpu_type_p = NULL;
        /* check if the CPU was already set to an available type */
        while (slist != NULL) {
            if (slist->monitor_cpu_type_p == monitor_cpu_for_memspace[i]) {
                monitor_cpu_type_p = slist->monitor_cpu_type_p;
            }
            slist = slist->next;
        }
        /* if no matching CPU was set, use the first supported one, if there is any */
        if (monitor_cpu_type_p == NULL && monitor_cpu_type_supported[i]) {
            monitor_cpu_for_memspace[i] = monitor_cpu_type_supported[i]->monitor_cpu_type_p;
        }
    }

#if 0
    monitor_cpu_for_memspace[e_comp_space] =
        monitor_cpu_type_supported[e_comp_space]->monitor_cpu_type_p;

    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        monitor_cpu_for_memspace[monitor_diskspace_mem(dnr)] =
            monitor_cpu_type_supported[monitor_diskspace_mem(dnr)]->monitor_cpu_type_p;
    }
#endif
    /* Safety precaution */
    /* FIXME: this makes no sense at all */
    /* monitor_cpu_for_memspace[default_memspace] = monitor_cpu_for_memspace[default_memspace]; */

    dot_addr[e_comp_space] = new_addr(e_comp_space, ((uint16_t)((monitor_cpu_for_memspace[e_comp_space]->mon_register_get_val)(e_comp_space, e_PC))));

    for (dnr = 0; dnr < NUM_DISK_UNITS; dnr++) {
        int mem = monitor_diskspace_mem(dnr);
        dot_addr[mem] = new_addr(mem, ((uint16_t)((monitor_cpu_for_memspace[mem]->mon_register_get_val)(mem, e_PC))));
    }

    mon_event_opened();

    /* disassemble at monitor entry, for single stepping */
    if (disassemble_on_entry) {
        int monbank = mon_interfaces[default_memspace]->current_bank;
        /* always disassemble using CPU bank */
        if (mon_interfaces[default_memspace]->mem_bank_from_name != NULL) {
            mon_interfaces[default_memspace]->current_bank = mon_interfaces[default_memspace]->mem_bank_from_name("cpu");
        } else {
            mon_interfaces[default_memspace]->current_bank = 0;
        }
        mon_disassemble_with_regdump(default_memspace, dot_addr[default_memspace]);
        mon_interfaces[default_memspace]->current_bank = monbank; /* restore value used in monitor */
        disassemble_on_entry = 0;
    }

#if 0
    /* FIXME: the first time the console opens the font might not be initialized
       correctly, so close and repopen the monitor once */
    {
        int ch;
        char str[2] = { 0, 0 };
        mon_out("petscii:\n");
        for (ch = 0; ch < 0x100; ch++) {
            if ((ch && !(ch & 0x0f))) { mon_out("\n"); }
            str[0] = ch;
            mon_petscii_out(1, "%s", str);
        }
        mon_out("\n\npetscii (upper):\n");
        for (ch = 0; ch < 0x100; ch++) {
            if ((ch && !(ch & 0x0f))) { mon_out("\n"); }
            str[0] = ch;
            mon_petscii_upper_out(1, "%s", str);
        }
        mon_out("\n\nscrcode:\n");
        for (ch = 0; ch < 0x100; ch++) {
            if ((ch && !(ch & 0x0f))) { mon_out("\n"); }
            str[0] = ch;
            mon_scrcode_out(1, "%s", str);
        }
        mon_out("\n\nscrcode (upper):\n");
        for (ch = 0; ch < 0x100; ch++) {
            if ((ch && !(ch & 0x0f))) { mon_out("\n"); }
            str[0] = ch;
            mon_scrcode_upper_out(1, "%s", str);
        }
        mon_out("\n");
    }
#endif
}

static int monitor_process(char *cmd)
{
    char *trimmed_command;

    mon_stop_output = 0;

    if (cmd == NULL) {
        mon_out("\n");
    } else {
        if (!cmd[0]) {
            if (!asm_mode) {
                /* Repeat previous command */
                lib_free(cmd);
                cmd = last_cmd ? lib_strdup(last_cmd) : NULL;
            } else {
                /* Leave asm mode */
            }
        }

        if (cmd) {
            if (recording) {

                trimmed_command = lib_strdup_trimmed(cmd);

                if (strcmp(trimmed_command, "stop") != 0) {
                    if (fprintf(recording_fp, "%s\n", trimmed_command) < 0) {
                        mon_out("Error while recording commands. Output file closed.\n");
                        fclose(recording_fp);
                        recording_fp = NULL;
                        recording = false;
                    }
                }
                lib_free(trimmed_command);
                trimmed_command = NULL;
            }
            parse_and_execute_line(cmd);
        }
    }
    lib_free(last_cmd);

    /* remember last command, except when leaving the monitor */
    if (exit_mon && mon_console_suspend_on_leaving) {
        lib_free(cmd);
        last_cmd = NULL;
    } else {
        last_cmd = cmd;
    }

    uimon_notify_change(); /* @SRT */

    return exit_mon;
}

static void monitor_close(bool check_exit)
{
#ifdef FEATURE_CPUMEMHISTORY
    memmap_state &= ~(MEMMAP_STATE_IN_MONITOR);
#endif
    inside_monitor = false;

    if (exit_mon) {
        exit_mon--;
    }

    if (check_exit && exit_mon) {
        if (!monitor_is_remote()) {
            uimon_window_close();
        }
        archdep_vice_exit(0);
    }

    exit_mon = 0;

    if (!monitor_is_remote() && !monitor_is_binary()) {
        if (mon_console_suspend_on_leaving) {
            /*
                if there is no log, or if the console can not stay open when the emulation
                runs, close the console. otherwise suspend the window.
            */
            if ((console_log == NULL) ||
                (mon_console_close_on_leaving == 1) ||
                (console_log->console_can_stay_open == 0) ||
                (keep_monitor_open == 0)) {
                uimon_window_close();
            } else {
                uimon_window_suspend();
            }
        }
    }

    mon_event_closed();

    if (mon_console_suspend_on_leaving) {
        console_log = NULL;
    }

    vsync_suspend_speed_eval();
}

void monitor_startup(MEMSPACE mem)
{
    char prompt[40];
    char *p;

    if (inside_monitor) {
        /*
         * Drive cpu interrupt can enter the monitor .. and then the monitor
         * can tell the drive to catch up, re-triggering the incomplete interrupt
         * .. recursion, etc
         */
        return;
    }

    if (ui_pause_active()) {
        should_pause_on_exit_mon = true;

        ui_pause_disable();
    }

    if (mem != e_default_space) {
        default_memspace = mem;
    }

    monitor_open();
    while (!exit_mon) {

        if (playback_fp) {
            playback_next_command();
        } else {
            /*
             * We are about to go interactive.
             */

            if (refresh_on_break) {
                /* Render all in-progress frames as we enter the prompt */
                video_canvas_refresh_all_tracked();
            }

            make_prompt(prompt);
            p = uimon_in(prompt);
            if (p) {
                exit_mon = monitor_process(p);
            } else if (exit_mon < 1) {
                mon_exit();
            }
        }

        if (exit_mon) {
            /* mon_out("exit\n"); */
            break;
        }
    }
    monitor_close(true);

    if (pause_on_exit_mon) {
        pause_on_exit_mon = false;

        ui_pause_enable();

        while (ui_pause_loop_iteration());
    }
}

static void monitor_trap(uint16_t addr, void *unused_data)
{
    monitor_startup(e_default_space);
}

void monitor_startup_trap(void)
{
    if (!inside_monitor) {
        interrupt_maincpu_trigger_trap(monitor_trap, 0);
    }
}

void mon_maincpu_trace(void)
{
#ifdef DEBUG
    int state = e_OFF;
    resources_get_int("MainCPU_TRACE", &state);
    mon_out("CPU tracing is %s.\n", state == e_ON ? "on" : "off");
#else
    mon_out("Not compiled with CPU tracing.\n");
#endif
}

void mon_maincpu_toggle_trace(int state)
{
#ifdef DEBUG
    if (state == e_TOGGLE) {
        int old_state;
        if (!resources_get_int("MainCPU_TRACE", &old_state)) {
            return;
        }
        state = old_state ? e_OFF : e_ON;
    }
    resources_set_int("MainCPU_TRACE", state);
#endif
    mon_maincpu_trace();
}
